package com.jf.cloud.multishop.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.jf.cloud.api.leaf.feign.SegmentFeignClient;
import com.jf.cloud.api.order.constant.PurchaseOrderWaitStatus;
import com.jf.cloud.common.cache.constant.LockCacheNames;
import com.jf.cloud.common.constant.Constant;
import com.jf.cloud.common.constant.DistributedIdKey;
import com.jf.cloud.common.exception.LuckException;
import com.jf.cloud.common.order.bo.OrderStatusBO;
import com.jf.cloud.common.order.bo.PayNotifyBO;
import com.jf.cloud.common.order.vo.OrderSimpleAmountInfoBO;
import com.jf.cloud.common.response.ResponseEnum;
import com.jf.cloud.common.response.ServerResponseEntity;
import com.jf.cloud.common.rocketmq.config.RocketMqConstant;
import com.jf.cloud.multishop.bo.mongo.MongoShopWalletLogBO;
import com.jf.cloud.multishop.constant.ShopWalletAmountType;
import com.jf.cloud.multishop.constant.ShopWalletChangeReason;
import com.jf.cloud.multishop.constant.ShopWalletZoneConstant;
import com.jf.cloud.multishop.dto.ShopWalletLogSearchDTO;
import com.jf.cloud.multishop.mapper.ShopWalletMapper;
import com.jf.cloud.multishop.mapper.ShopWalletZoneMapper;
import com.jf.cloud.multishop.service.ShopWalletLogService;
import com.jf.cloud.multishop.service.ShopWalletZoneService;
import com.jf.cloud.multishop.service.WalletLogOperationService;
import com.jf.cloud.multishop.vo.ShopWalletZoneInfoVO;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * 商家钱包信息
 *
 * @author zz
 * @date 2021-01-07 16:41:03
 */
@Service
public class WalletLogOperationServiceImpl implements WalletLogOperationService {

    @Autowired
    private ShopWalletMapper shopWalletMapper;
    @Autowired
    private ShopWalletLogService shopWalletLogService;
    @Autowired
    private SegmentFeignClient segmentFeignClient;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private RocketMQTemplate shopWalletNotifyServiceTemplate;
    @Autowired
    private ShopWalletZoneService shopWalletZoneService;
    @Autowired
    private ShopWalletZoneMapper shopWalletZoneMapper;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RocketMQTemplate orderSettledSupplierTemplate;

    private static final Logger logger = LoggerFactory.getLogger(WalletLogOperationServiceImpl.class);

    /**
     * TODO -supplier
     * @param orderSimpleAmountInfos 订单id和店铺id关联信息
     * @param message 订单id和店铺id关联信息
     * @param settledAmountMap 店铺已结算金额map
     * @param salesMap 商家的代销配置
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addAmountByOrder(List<OrderSimpleAmountInfoBO> orderSimpleAmountInfos, PayNotifyBO message, Map<Long, Long> settledAmountMap, Map<Long, Integer> salesMap) {

        // 进行分账，添加一个分账记录表，因为每一个订单进行分完帐后要进行退款，那么退款的时候也应该按照下单的时候的分账比例进行原路返回
        // 获取当前分账的比例
        List<OrderStatusBO> orderStatusList = new ArrayList<>();
        List<MongoShopWalletLogBO> shopWalletLogList = new ArrayList<>();
        List<OrderSimpleAmountInfoBO> orderSupplierAmountInfos = new ArrayList<>();
        for (OrderSimpleAmountInfoBO orderSimpleAmountInfo : orderSimpleAmountInfos) {
            Long settlementAmount = 0L;
            // 商家之所以会从一笔订单拿到钱，是因为订单支付成功
            // 幂等
            // 1. 商家应收 = 商品价格 - 商家优惠 - 分销金额
            // 2. 商家应收 = 用户支付 + 平台补贴 - 分销金额
            Long changeAmount;
            Long supplierAmount = null;
            changeAmount = orderSimpleAmountInfo.getActualTotal() + orderSimpleAmountInfo.getPlatformAmount() - orderSimpleAmountInfo.getDistributionAmount() - orderSimpleAmountInfo.getPlatformCommission();
            if(!Objects.equals(orderSimpleAmountInfo.getSupplierId(),0L)) {
                // 代销订单处理
                // 商家余额
                settlementAmount = settledAmountMap.get(orderSimpleAmountInfo.getShopId());
                // 如果商家收入金额小于供货价，0.直接转换为待采购订单（默认选中），1.通过商家已结算金额扣除差价
                Integer type = salesMap.get(orderSimpleAmountInfo.getShopId());
                // 供应商收入金额为 = 采购金额 + 运费 + 商家优惠运费 - 平台佣金
                supplierAmount = orderSimpleAmountInfo.getPurchaseAmount() + orderSimpleAmountInfo.getFreightAmount() + Math.abs(orderSimpleAmountInfo.getFreeFreightAmount());
                // 商家实际待结算金额 = 之前算法 - 运费 - 采购金额
                changeAmount = changeAmount - supplierAmount;
                // 如果商家收入金额小于供货价，判断处理
                // 代销设置为商家通过已结算账户金额扣除差价，且已结算金额小于于差价则变成待采购订单
                // 情况一，代销设置为商家直接转换成采购订单，且已结算金额小于差价则转换成采购订单
//                boolean checkAmount = (Objects.equals(type,0) && changeAmount < 0);
                // 情况二，代销设置为商家通过已结算账户金额扣除差价，且已结算金额小于于差价则变成待采购订单
                boolean checkAmountFlag =(Objects.equals(type,0) && changeAmount < 0) || (Objects.equals(type,1) && settlementAmount + changeAmount < 0);
                // 商家不同意采购，或者情况一，或者情况二，订单变成待采购
                if(!Objects.equals(message.getAgreePurchase(),1) && checkAmountFlag){
                    OrderStatusBO orderStatusBO = new OrderStatusBO();
                    orderStatusBO.setOrderId(orderSimpleAmountInfo.getOrderId());
                    orderStatusBO.setWaitPurchase(1);
                    orderStatusBO.setPurchaseSpreadAmount(Math.abs(changeAmount));
                    orderStatusList.add(orderStatusBO);
                    continue;
                }
                // 自动采购完成，代销设置为商家通过已结算账户金额自动扣除差价，且已结算金额足够，且改变金额小于0
                if(changeAmount < 0 && Objects.equals(type,1) && settlementAmount + changeAmount > 0){
                    OrderStatusBO orderStatusBO = new OrderStatusBO();
                    orderStatusBO.setOrderId(orderSimpleAmountInfo.getOrderId());
                    orderStatusBO.setWaitPurchase(PurchaseOrderWaitStatus.PURCHASED.value());
                    orderStatusBO.setPurchaseSpreadAmount(Math.abs(changeAmount));
                    orderStatusList.add(orderStatusBO);
                }
                supplierAmount = supplierAmount - orderSimpleAmountInfo.getPurchasePlatformCommission();
                // 处理下供应商的分账
                handleSupplierAmount(orderSupplierAmountInfos, orderSimpleAmountInfo, changeAmount, supplierAmount, shopWalletLogList);
                // 待结算金额处理
                changeAmount = Math.max(0L,changeAmount);
            }

            // 商家添加未结算金额
            Integer zoneNo = addUnsettledAmount(orderSimpleAmountInfo.getShopId(), changeAmount, null);
            // 平台添加未结算金额（平台佣金 - 平台优惠分摊优惠金额）
            long platformChangeAmount = orderSimpleAmountInfo.getPlatformCommission() + orderSimpleAmountInfo.getPurchasePlatformCommission() - orderSimpleAmountInfo.getPlatformAmount();
//            shopWalletMapper.addUnsettledAmount(Constant.PLATFORM_SHOP_ID, platformChangeAmount);

            // 获取商家钱包记录列表
            saveShopWalletLog(orderSimpleAmountInfo, changeAmount, platformChangeAmount, shopWalletLogList, zoneNo);
        }

//        // 批量设置商家钱包记录id - 存店铺钱包记录到mongodb，因为上面保证了幂等性所以这里可以直接生成id保存到数据库
//        if (CollUtil.isNotEmpty(shopWalletLogList)) {
//            for (MongoShopWalletLogBO mongoShopWalletLogBO : shopWalletLogList) {
//                mongoShopWalletLogBO.setWalletLogId(message.getShopWalletIds().remove(0));
//            }
//        }

//        // 这里是回复供应商记录，商家的不用再发了
//        SendStatus sendWalletLogStatus = addShopWalletLogTemplate.syncSend(RocketMqConstant.ADD_SHOP_WALLET_LOG_TOPIC, new GenericMessage<>(shopWalletLogList)).getSendStatus();
//        if (!Objects.equals(sendWalletLogStatus, SendStatus.SEND_OK)) {
//            throw new LuckException(ResponseEnum.EXCEPTION);
//        }
        if(CollectionUtil.isNotEmpty(orderSupplierAmountInfos)) {
            ServerResponseEntity<List<Long>> segmentEntity = segmentFeignClient.batchGetSegmentId(DistributedIdKey.MALL4CLOUD_SHOP_WALLET_LOG, orderSupplierAmountInfos.size());
            if (segmentEntity.isFail()) {
                throw new LuckException(ResponseEnum.EXCEPTION);
            }
            for (OrderSimpleAmountInfoBO orderSimpleAmountInfo : orderSupplierAmountInfos) {
                orderSimpleAmountInfo.setWalletLogId(segmentEntity.getData().remove(0));
            }
            message.setOrderSimpleAmountInfos(orderSupplierAmountInfos);
        }

        // 分账总消息，包括分销分支&&订单采购&&供应商分账
        // 分销订单有自己的操作，分销订单不包含积分订单
        // 订单成功 ----> 商家分账成功 ----> 分销分账 ---> 供应商分账
        // 代销商品流程 订单成功 ----> 商家分账成功 ----> 分销分账 ---> 供应商分账 --->修改订单为待采购订单
        SendStatus shopWalletNotifyStatus = shopWalletNotifyServiceTemplate.syncSend(RocketMqConstant.SHOP_WALLET_NOTIFY_SERVICE_TOPIC, new GenericMessage<>(message)).getSendStatus();
        if (!Objects.equals(shopWalletNotifyStatus, SendStatus.SEND_OK)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addAmountBySettledOrder(Long orderId) {

//        // 商家之所以会从一笔订单拿到钱，是因为确认收货 or 订单退款时有部分不退
//        long count = shopWalletLogService.countInAmountByOrderId(orderId, ShopWalletChangeReason.SETTLED.value());
//        // 幂等
//        if (count > 0) {
//            return;
//        }
        ShopWalletLogSearchDTO shopWalletLogSearchDTO = new ShopWalletLogSearchDTO();
        shopWalletLogSearchDTO.setOrderId(orderId);
        List<MongoShopWalletLogBO> shopWalletLogs = shopWalletLogService.listByParam(shopWalletLogSearchDTO);

        // 商家&平台需要结算的钱
        MongoShopWalletLogBO payLog = null;

        MongoShopWalletLogBO shopRefundLog = new MongoShopWalletLogBO();
        shopRefundLog.setUserAmount(0L);
        shopRefundLog.setDistributionAmount(0L);
        shopRefundLog.setPlatformAmount(0L);
        shopRefundLog.setShopPlatformCommission(0L);
        shopRefundLog.setPlatformCommission(0L);
        shopRefundLog.setShopChangeAmount(0L);
        shopRefundLog.setPlatformChangeAmount(0L);
        for (MongoShopWalletLogBO shopWalletLog : shopWalletLogs) {
            // 下单
            MongoShopWalletLogBO shopPayLog = polymerizationShopWalletLog(shopRefundLog, shopWalletLog);
            if (shopPayLog != null) {
                payLog = shopPayLog;
            }
        }
        // 非积分订单需要保存店铺结算记录
        if (Objects.nonNull(payLog)) {
//            // 保存店铺结算记录
//            saveSettledLog(payLog, shopRefundLog);
            // 通知供应商供应商结算
            SendStatus sendSupplierStatus = orderSettledSupplierTemplate.syncSend(RocketMqConstant.ORDER_SETTLED_SUPPLIER_TOPIC, new GenericMessage<>(orderId)).getSendStatus();
            if (!Objects.equals(sendSupplierStatus, SendStatus.SEND_OK)) {
                throw new LuckException(ResponseEnum.EXCEPTION);
            }
        }

    }



    private void saveShopWalletLog(OrderSimpleAmountInfoBO orderSimpleAmountInfo, Long changeAmount, long platformChangeAmount, List<MongoShopWalletLogBO> shopWalletLogList, Integer zoneNo) {
        MongoShopWalletLogBO shopWalletLog = new MongoShopWalletLogBO();
        shopWalletLog.setOrderId(orderSimpleAmountInfo.getOrderId());
        shopWalletLog.setZoneNo(zoneNo);
        shopWalletLog.setCreateTime(new Date());
        shopWalletLog.setUpdateTime(new Date());
        shopWalletLog.setReason(ShopWalletChangeReason.PAY.value());
        shopWalletLog.setShopId(orderSimpleAmountInfo.getShopId());
        shopWalletLog.setAmountType(ShopWalletAmountType.UNSETTLED_AMOUNT.value());
        shopWalletLog.setPlatformIoType(platformChangeAmount >= 0 ? 1 : 0);

        shopWalletLog.setUserAmount(orderSimpleAmountInfo.getActualTotal());
        shopWalletLog.setPlatformCommission(orderSimpleAmountInfo.getPlatformCommission() + orderSimpleAmountInfo.getPurchasePlatformCommission());
        shopWalletLog.setPlatformChangeAmount(shopWalletLog.getUserAmount());
        shopWalletLog.setDistributionAmount(orderSimpleAmountInfo.getDistributionAmount());
        shopWalletLog.setPlatformAmount(orderSimpleAmountInfo.getPlatformAmount());

        shopWalletLog.setShopReduceAmount(orderSimpleAmountInfo.getShopAmount());
        shopWalletLog.setShopPlatformCommission(orderSimpleAmountInfo.getPlatformCommission());

        if (!Objects.equals(orderSimpleAmountInfo.getShopId(), Constant.PLATFORM_SHOP_ID)) {
            shopWalletLog.setShopIoType(1);
            shopWalletLog.setShopChangeAmount(changeAmount);
            shopWalletLog.setShopPlatformCommission(orderSimpleAmountInfo.getPlatformCommission());
            shopWalletLog.setPlatformChangeAmount(Math.abs(platformChangeAmount));
        }
        shopWalletLogList.add(shopWalletLog);
    }

    /**
     * 供应商采购金额处理，如果商家待结算金额改变金额小于0，减少结算金额并添加日志
     * @param orderSupplierAmountInfos 采购分账信息
     * @param orderSimpleAmountInfo 订单信息
     * @param changeAmount 商家待结算改变金额
     * @param supplierAmount 供应商收入金额
     */
    private void handleSupplierAmount(List<OrderSimpleAmountInfoBO> orderSupplierAmountInfos, OrderSimpleAmountInfoBO orderSimpleAmountInfo,
                                      Long changeAmount, Long supplierAmount, List<MongoShopWalletLogBO> shopWalletLogList) {
        OrderSimpleAmountInfoBO supplierAmountBO = new OrderSimpleAmountInfoBO();
        supplierAmountBO.setOrderId(orderSimpleAmountInfo.getOrderId());
        supplierAmountBO.setPurchasePlatformCommission(orderSimpleAmountInfo.getPurchasePlatformCommission());
        supplierAmountBO.setPurchaseAmount(supplierAmount);
        supplierAmountBO.setActualTotal(orderSimpleAmountInfo.getActualTotal());
        supplierAmountBO.setSupplierId(orderSimpleAmountInfo.getSupplierId());
        supplierAmountBO.setFreightAmount(orderSimpleAmountInfo.getFreightAmount());
        supplierAmountBO.setFreeFreightAmount(orderSimpleAmountInfo.getFreeFreightAmount());
        orderSupplierAmountInfos.add(supplierAmountBO);

        if(changeAmount < 0) {
            RLock lock = redissonClient.getLock(LockCacheNames.LOCK_SHOP_WALLET_ZONE_PREFIX + orderSimpleAmountInfo.getShopId());
            try {
                lock.lock();
                // 代销订单，商家减少结算金额并添加日志
                boolean isSuccess = subSettledAmount(orderSimpleAmountInfo.getShopId(), Math.abs(changeAmount));
                if (!isSuccess) {
                    throw new LuckException("店铺结算金额不足");
                }
            } finally {
                lock.unlock();
            }
            // 添加日志
            MongoShopWalletLogBO shopWalletLog = new MongoShopWalletLogBO();
            shopWalletLog.setCreateTime(new Date());
            shopWalletLog.setUpdateTime(shopWalletLog.getCreateTime());
            shopWalletLog.setOrderId(orderSimpleAmountInfo.getOrderId());
            shopWalletLog.setReason(ShopWalletChangeReason.PURCHASE_SPREAD_AMOUNT.value());
            shopWalletLog.setShopId(orderSimpleAmountInfo.getShopId());
            shopWalletLog.setShopIoType(0);
            shopWalletLog.setShopChangeAmount(changeAmount);
            shopWalletLog.setAmountType(ShopWalletAmountType.SETTLED_AMOUNT.value());
            shopWalletLog.setUserAmount(orderSimpleAmountInfo.getActualTotal());
            shopWalletLog.setDistributionAmount(orderSimpleAmountInfo.getDistributionAmount());
            shopWalletLog.setShopReduceAmount(orderSimpleAmountInfo.getShopAmount());
            shopWalletLog.setPlatformAmount(orderSimpleAmountInfo.getPlatformAmount());
            shopWalletLog.setShopPlatformCommission(orderSimpleAmountInfo.getPlatformCommission());
            // 添加日志
            shopWalletLogList.add(shopWalletLog);
        }
    }


    /**
     * 代销订单，商家减少结算金额
     */
    public boolean subSettledAmount(Long shopId, Long changeAmount) {
        if (Objects.equals(changeAmount, 0L)) {
            return true;
        }

        ShopWalletZoneInfoVO shopZoneInfo = shopWalletZoneService.getShopZoneInfo(shopId);
        if (Objects.equals(shopZoneInfo.getZoneNum(), ShopWalletZoneConstant.ZERO)) {
            // 未分片就直接按原来的方式扣减结算金额
            return shopWalletMapper.subSettledAmount(shopId,changeAmount) > 0;
        } else {
            Random random = new Random();
            int zoneNo = random.nextInt(shopZoneInfo.getZoneNum());

            // 1. 通过次分片扣减
            boolean updateSuccess = shopWalletZoneMapper.subSettledAmount(shopId, changeAmount, zoneNo) > 0;
            if (updateSuccess) {
                return true;
            }

            // 2. 通过次分片扣减失败，因为次分片金额不够，那么尝试从主分片进行扣减，虽然是均分但是有可能商家充值了或者次分片被扣钱了之类的主分片的结算金额比次分片的要多
            boolean mainUpdateSuccess = shopWalletMapper.subSettledAmount(shopId, changeAmount) > 0;
            if (mainUpdateSuccess) {
                return true;
            }

            // 3. 如果主分片也不够扣，再把所有分片统计到主分片进行扣减之后重新分配分片金额
            // 聚合统计结算金额
            Long mainSettleAmount = shopWalletMapper.getSettlementAmountByShopId(shopId);

            Long sumLessZoneSettleAmount = shopWalletZoneMapper.getSettleAmountByShopId(shopId);

            long totalSettleAmount = mainSettleAmount + sumLessZoneSettleAmount;

            if (totalSettleAmount < changeAmount) {
                return false;
            }

            // 剩余的结算金额
            long remainSettleAmount = totalSettleAmount - changeAmount;
            // 把所有的结算金额再次均分
            shopWalletZoneService.updateZoneSettleAmount(shopId, remainSettleAmount, shopZoneInfo.getZoneNum());
            return true;
        }
    }

    /**
     * 聚合订单支付退款导致商家金额变更的记录
     * @param refundWalletLog
     * @param shopWalletLog
     * @return
     */
    private MongoShopWalletLogBO polymerizationShopWalletLog(MongoShopWalletLogBO refundWalletLog, MongoShopWalletLogBO shopWalletLog) {
        if (Objects.equals(ShopWalletChangeReason.PAY.value(), shopWalletLog.getReason())) {
            return shopWalletLog;
        }
        // 退款
        else if (Objects.equals(ShopWalletChangeReason.ORDER_REFUND.value(), shopWalletLog.getReason())) {
            refundWalletLog.setUserAmount(refundWalletLog.getUserAmount() + shopWalletLog.getUserAmount());
            refundWalletLog.setDistributionAmount(refundWalletLog.getDistributionAmount() + shopWalletLog.getDistributionAmount());
            refundWalletLog.setPlatformAmount(refundWalletLog.getPlatformAmount() + shopWalletLog.getPlatformAmount());
            refundWalletLog.setShopPlatformCommission(refundWalletLog.getShopPlatformCommission() +
                    (Objects.nonNull(shopWalletLog.getShopPlatformCommission()) ? shopWalletLog.getShopPlatformCommission() : 0L));
            refundWalletLog.setPlatformCommission(refundWalletLog.getPlatformCommission() +
                    (Objects.nonNull(shopWalletLog.getPlatformCommission()) ? shopWalletLog.getPlatformCommission() : 0L));
            // 如果因为退款收入，实际上是减少了退款的钱
            Long shopChangeAmount = (Objects.isNull(shopWalletLog.getShopIoType()) ? 0L :
                    (shopWalletLog.getShopIoType() == 1) ? -shopWalletLog.getShopChangeAmount() : shopWalletLog.getShopChangeAmount());
            Long platformChangeAmount = (Objects.isNull(shopWalletLog.getPlatformIoType()) ? 0L :
                    (shopWalletLog.getPlatformIoType() == 1) ? -shopWalletLog.getPlatformChangeAmount() : shopWalletLog.getPlatformChangeAmount());
            refundWalletLog.setShopChangeAmount(refundWalletLog.getShopChangeAmount() + shopChangeAmount);
            refundWalletLog.setPlatformChangeAmount(refundWalletLog.getPlatformChangeAmount() + platformChangeAmount);
        }
        return null;
    }


    /**
     * 下单添加待结算金额/分销减少待结算金额
     * 返回分片的序号，之后退款或者分销结算或者订单结算要扣待结算金额时根据分片序号和店铺id来扣减
     * 如果没有分片的话返回null，判断如果是空或就从主分片扣减
     */
    private Integer addUnsettledAmount(Long shopId, Long changeAmount, Integer zoneNo) {
        ShopWalletZoneInfoVO shopZoneInfo = shopWalletZoneService.getShopZoneInfo(shopId);
        if (Objects.equals(shopZoneInfo.getZoneNum(), ShopWalletZoneConstant.ZERO)) {
            // 未分片就直接按原来的方式增加(因为添加了唯一索引，所以是不用怕多次加钱)
            shopWalletMapper.addUnsettledAmount(shopId, changeAmount);
            return null;
        } else {
            // 通过分片增加待结算金额
            if (Objects.isNull(zoneNo)) {
                Random random = new Random();
                zoneNo = random.nextInt(shopZoneInfo.getZoneNum());
            }
            // 如果是支付增加的待结算金额，这里的待结算金额不会为负
            // 如果是分销进来的这个方法，会根据这笔订单增加的待结算金额的钱包的zoneNo去进行扣减金额，结果也不会是负的
            shopWalletZoneMapper.addUnsettledAmount(shopId, changeAmount, zoneNo);
            return zoneNo;
        }
    }
}
